import angular from 'angular';
import template from './ng-date-picker.template.html';

ngDatePickerDirective.$inject = ['$document', '$timeout'];

function ngDatePickerDirective($document, $timeout) {
  return {
    restrict: 'E',
    template,
    scope: {
      ngModel: '=',
      placeholder: '@?',
      format: '@?',
      minDate: '=?',
      maxDate: '=?',
      disabled: '=?',
      clearable: '=?',
      showToday: '=?',
      showClear: '=?',
      startWeekOnMonday: '=?'
    },
    link: function(scope, element) {
      scope.placeholder = scope.placeholder || '请选择日期';
      scope.format = scope.format || 'YYYY-MM-DD';
      scope.clearable = scope.clearable !== undefined ? scope.clearable : true;
      scope.showToday = scope.showToday !== undefined ? scope.showToday : true;
      scope.showClear = scope.showClear !== undefined ? scope.showClear : scope.clearable;
      scope.disabled = scope.disabled || false;
      scope.startWeekOnMonday = scope.startWeekOnMonday || false;

      scope.isOpen = false;
      scope.displayValue = '';
      scope.currentMonth = startOfMonth(new Date());
      scope.currentMonthLabel = '';
      scope.weekdayLabels = getWeekdayLabels(scope.startWeekOnMonday);
      scope.days = [];

      var documentClickHandler = null;
      var modelType = null;

      function ensureModelType() {
        if (modelType) return;

        var current = scope.ngModel;

        if (angular.isString(current) || current === '') {
          modelType = 'string';
        } else if (angular.isDate(current)) {
          modelType = 'date';
        } else {
          modelType = 'date';
        }
      }

      function writeModel(date) {
        ensureModelType();

        if (!date) {
          scope.ngModel = modelType === 'string' ? '' : null;
          return;
        }

        if (modelType === 'string') {
          scope.ngModel = formatDate(date, scope.format);
        } else {
          scope.ngModel = startOfDay(date);
        }
      }

      function parseDate(value) {
        if (!value) return null;

        if (angular.isDate(value)) {
          return isNaN(value.getTime()) ? null : new Date(value.getTime());
        }

        if (angular.isString(value)) {
          var normalized = value.trim().replace(/[-.]/g, '/');
          var parsed = new Date(normalized);
          return isNaN(parsed.getTime()) ? null : startOfDay(parsed);
        }

        return null;
      }

      function formatDate(date, format) {
        if (!date) return '';

        var year = date.getFullYear();
        var month = pad(date.getMonth() + 1);
        var day = pad(date.getDate());

        return format
          .replace('YYYY', year)
          .replace('MM', month)
          .replace('DD', day);
      }

      function pad(num) {
        return num < 10 ? '0' + num : '' + num;
      }

      function getWeekdayLabels(startOnMonday) {
        return startOnMonday
          ? ['一', '二', '三', '四', '五', '六', '日']
          : ['日', '一', '二', '三', '四', '五', '六'];
      }

      function startOfDay(date) {
        var result = new Date(date.getTime());
        result.setHours(0, 0, 0, 0);
        return result;
      }

      function startOfMonth(date) {
        var result = startOfDay(date);
        result.setDate(1);
        return result;
      }

      function isSameDay(a, b) {
        if (!a || !b) return false;
        return a.getFullYear() === b.getFullYear() &&
          a.getMonth() === b.getMonth() &&
          a.getDate() === b.getDate();
      }

      function clampDate(date) {
        var min = parseDate(scope.minDate);
        var max = parseDate(scope.maxDate);
        var value = startOfDay(date);

        if (min && value.getTime() < min.getTime()) {
          return min;
        }

        if (max && value.getTime() > max.getTime()) {
          return max;
        }

        return value;
      }

      function isDateDisabled(date) {
        var min = parseDate(scope.minDate);
        var max = parseDate(scope.maxDate);

        if (min && date.getTime() < min.getTime()) return true;
        if (max && date.getTime() > max.getTime()) return true;

        return false;
      }

      function buildCalendar() {
        var base = scope.currentMonth;
        scope.currentMonthLabel = base.getFullYear() + '年' + pad(base.getMonth() + 1) + '月';

        var firstDay = new Date(base.getFullYear(), base.getMonth(), 1);
        var firstWeekday = firstDay.getDay();

        if (scope.startWeekOnMonday) {
          firstWeekday = (firstWeekday + 6) % 7;
        }

        var daysInMonth = new Date(base.getFullYear(), base.getMonth() + 1, 0).getDate();
        var prevMonthDays = firstWeekday;
        var totalCells = 42; // 6 weeks
        var today = startOfDay(new Date());
        var selected = parseDate(scope.ngModel);

        var days = [];

        for (var i = 0; i < totalCells; i++) {
          var dayOffset = i - prevMonthDays + 1;
          var currentDate = new Date(base.getFullYear(), base.getMonth(), dayOffset);
          var outside = currentDate.getMonth() !== base.getMonth();
          var disabled = scope.disabled || isDateDisabled(currentDate);

          days.push({
            key: currentDate.toISOString(),
            date: currentDate,
            isOutside: outside,
            isToday: isSameDay(currentDate, today),
            isSelected: selected ? isSameDay(currentDate, selected) : false,
            isDisabled: disabled
          });
        }

        scope.days = days;
      }

      function updateDisplay() {
        var parsed = parseDate(scope.ngModel);

        if (parsed) {
          var clamped = clampDate(parsed);
          if (clamped.getTime() !== parsed.getTime()) {
            writeModel(clamped);
            parsed = clamped;
          }
        }

        scope.displayValue = parsed ? formatDate(parsed, scope.format) : '';

        if (parsed) {
          scope.currentMonth = startOfMonth(parsed);
        }

        buildCalendar();
      }

      scope.togglePicker = function($event) {
        if ($event) {
          $event.stopPropagation();
        }

        if (scope.disabled) return;

        scope.isOpen = !scope.isOpen;

        if (scope.isOpen) {
          buildCalendar();

          $timeout(function() {
            if (!documentClickHandler) {
              documentClickHandler = function(event) {
                var isInside = element[0].contains(event.target);
                if (!isInside) {
                  scope.$apply(function() {
                    scope.isOpen = false;
                  });
                }
              };

              $document.on('click', documentClickHandler);
            }
          });
        } else {
          detachDocumentHandler();
        }
      };

      function detachDocumentHandler() {
        if (documentClickHandler) {
          $document.off('click', documentClickHandler);
          documentClickHandler = null;
        }
      }

      scope.prevMonth = function() {
        scope.currentMonth = startOfMonth(new Date(scope.currentMonth.getFullYear(), scope.currentMonth.getMonth() - 1, 1));
        buildCalendar();
      };

      scope.nextMonth = function() {
        scope.currentMonth = startOfMonth(new Date(scope.currentMonth.getFullYear(), scope.currentMonth.getMonth() + 1, 1));
        buildCalendar();
      };

      scope.selectDay = function(day) {
        if (!day || day.isDisabled) return;

        var value = clampDate(day.date);
        writeModel(value);
        scope.isOpen = false;
        detachDocumentHandler();

        updateDisplay();
      };

      scope.selectToday = function() {
        if (scope.disabled) return;

        var today = startOfDay(new Date());

        if (isDateDisabled(today)) {
          return;
        }

        writeModel(today);
        scope.isOpen = false;
        detachDocumentHandler();
        updateDisplay();
      };

      scope.clear = function($event) {
        if ($event) {
          $event.stopPropagation();
        }

        if (scope.disabled) return;

        writeModel(null);
        scope.isOpen = false;
        detachDocumentHandler();
        updateDisplay();
      };

      scope.$watch(function() {
        return scope.ngModel;
      }, function(newVal) {
        if (modelType === null) {
          if (angular.isString(newVal) || newVal === '') {
            modelType = 'string';
          } else if (angular.isDate(newVal)) {
            modelType = 'date';
          }
        }
      });

      scope.$watchGroup(['ngModel', 'minDate', 'maxDate', 'format'], function() {
        updateDisplay();
      });

      scope.$watch('startWeekOnMonday', function(newVal, oldVal) {
        var normalized = !!newVal;

        if (scope.startWeekOnMonday === normalized && newVal === oldVal) {
          return;
        }

        scope.startWeekOnMonday = normalized;
        scope.weekdayLabels = getWeekdayLabels(normalized);
        buildCalendar();
      });

      scope.$watch('disabled', function(newVal) {
        if (newVal) {
          scope.isOpen = false;
          detachDocumentHandler();
        }
      });

      scope.$on('$destroy', function() {
        detachDocumentHandler();
      });

      updateDisplay();
    }
  };
}

export default ngDatePickerDirective;
